package raft

import "sync/atomic"

//
// example code to send a RequestVote RPC to a server.
// server is the index of the target server in rf.peers[].
// expects RPC arguments in args.
// fills in *reply with RPC reply, so caller should
// pass &reply.
// the types of the args and reply passed to Call() must be
// the same as the types of the arguments declared in the
// handler function (including whether they are pointers).
//
// The labrpc package simulates a lossy network, in which servers
// may be unreachable, and in which requests and replies may be lost.
// Call() sends a request and waits for a reply. If a reply arrives
// within a timeout interval, Call() returns true; otherwise
// Call() returns false. Thus Call() may not return for a while.
// A false return can be caused by a dead server, a live server that
// can't be reached, a lost request, or a lost reply.
//
// Call() is guaranteed to return (perhaps after a delay) *except* if the
// handler function on the server side does not return.  Thus there
// is no need to implement your own timeouts around Call().
//
// look at the comments in ../labrpc/labrpc.go for more details.
//
// if you're having trouble getting RPC to work, check that you've
// capitalized all field names in structs passed over RPC, and
// that the caller passes the address of the reply struct with &, not
// the struct itself.
//
func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {
	ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
	return ok
}

func (rf *Raft) attempRequestVote() {

	rf.mu.Lock()

	atomic.StoreInt32(&rf.state, Candidate)
	rf.currentTerm += 1
	rf.votedFor = rf.me
	rf.persist()

	term := rf.currentTerm

	lastLogIndex := rf.getLastLogIndex()
	lastLogTerm := rf.getLastLogTerm()

	vote := 1

	DPrintf("[%d] start election at term %d", rf.me, rf.currentTerm)

	rf.mu.Unlock()

	for i := range rf.peers {
		if i == rf.me {
			continue
		}

		go func(server int) {
			args := &RequestVoteArgs{
				Term:         term,
				CandidateId:  rf.me,
				LastLogTerm:  lastLogTerm,
				LastLogIndex: lastLogIndex,
			}

			reply := &RequestVoteReply{}

			ok := rf.sendRequestVote(server, args, reply)
			if !ok {
				return
			}

			rf.mu.Lock()
			defer rf.mu.Unlock()

			if !reply.VoteGranted {
				if reply.Term > rf.currentTerm {
					rf.currentTerm = reply.Term
					atomic.StoreInt32(&rf.state, Follower)
					rf.persist()
					rf.tickerCh <- struct{}{}
				}
				return
			}

			vote++
			DPrintf("[%d] got a vote from %d at term %d", rf.me, server, term)

			state := atomic.LoadInt32(&rf.state)
			if vote <= len(rf.peers)/2 || state != Candidate || rf.currentTerm != term {
				return
			}

			atomic.StoreInt32(&rf.state, Leader)
			rf.nextIndex = []int{}
			rf.matchIndex = []int{}

			for i := 0; i < len(rf.peers); i++ {
				rf.nextIndex = append(rf.nextIndex, rf.getLastLogIndex()+1)
				rf.matchIndex = append(rf.matchIndex, 0)
			}

			rf.tickerCh <- struct{}{}
			DPrintf("[%d] convert to leader", rf.me)
		}(i)
	}
}
